package psd.model.Prolog;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

public class IDResolver
implements IIDResolver
{

protected HashMap localBindings;
protected HashMap knownIDs;
protected IFQNTranslator fqnManager;
protected IIDGenerator provider;
private ASTNode currentlyProcessedNode;

public IDResolver(IFQNTranslator fqn, IIDGenerator provider)
{
    localBindings = new HashMap();
    knownIDs = new HashMap();
    fqnManager = fqn;
    this.provider = provider;
}

public String getID()
{
    return provider.getID();
}

public String getID(ASTNode node)
{
    String rv = "'null'";
    if(knownIDs.containsKey(node))
    {
        return knownIDs.get(node).toString();
    }
    if(node == null)
    {
        knownIDs.put(node, rv);
        return rv;
    }
    if(node instanceof PackageDeclaration)
    {
        PackageDeclaration pd = (PackageDeclaration)node;
        rv = getID(((IBinding) (pd.resolveBinding())));
    } else
    if(node instanceof AbstractTypeDeclaration)
    {
        AbstractTypeDeclaration td = (AbstractTypeDeclaration)node;
        ITypeBinding binding = td.resolveBinding();
        if((td.getParent() instanceof AnonymousClassDeclaration) || td.isLocalTypeDeclaration() || binding.getQualifiedName().equals(""))
        {
            rv = provider.getID();
            localBindings.put(binding, rv);
        } else
        {
            rv = getID(((IBinding) (binding)));
        }
    } else
    if(node instanceof AnonymousClassDeclaration)
    {
        AnonymousClassDeclaration td = (AnonymousClassDeclaration)node;
        rv = provider.getID();
        localBindings.put(td.resolveBinding(), rv);
    } else
    if(node instanceof VariableDeclarationFragment)
    {
        VariableDeclarationFragment vdf = (VariableDeclarationFragment)node;
        if((node.getParent() instanceof FieldDeclaration) && !isMemberOfLocalClass(node.getParent()))
        {
            rv = getID(((IBinding) (vdf.resolveBinding())));
        } else
        {
            rv = provider.getID();
            localBindings.put(vdf.resolveBinding(), rv);
        }
    } else
    if(node instanceof SingleVariableDeclaration)
    {
        SingleVariableDeclaration vdf = (SingleVariableDeclaration)node;
        String id = provider.getID();
        localBindings.put(vdf.resolveBinding(), id);
        rv = id;
    } else
    if(node instanceof MethodDeclaration)
    {
        if(isMemberOfLocalClass(node))
        {
            rv = provider.getID();
            localBindings.put(((MethodDeclaration)node).resolveBinding(), rv);
        } else
        {
            MethodDeclaration md = (MethodDeclaration)node;
            try
            {
                rv = getID(((IBinding) (md.resolveBinding())));
            }
            catch(IllegalArgumentException _ex)
            {
                System.err.println("DEBUG");
            }
        }
    } else
    if(node instanceof AnnotationTypeMemberDeclaration)
    {
        if(isMemberOfLocalClass(node))
        {
            rv = provider.getID();
            localBindings.put(((AnnotationTypeMemberDeclaration)node).resolveBinding(), rv);
        } else
        {
            AnnotationTypeMemberDeclaration md = (AnnotationTypeMemberDeclaration)node;
            try
            {
                rv = getID(((IBinding) (md.resolveBinding())));
            }
            catch(IllegalArgumentException _ex)
            {
                System.err.println("DEBUG");
            }
        }
    } else
    {
        rv = provider.getID();
    }
    knownIDs.put(node, rv);
    return rv;
}

private boolean isMemberOfLocalClass(ASTNode member)
{
    if(member.getParent() instanceof AnonymousClassDeclaration)
    {
        return true;
    }
    if(member.getParent() instanceof AbstractTypeDeclaration)
    {
        return ((AbstractTypeDeclaration)member.getParent()).isLocalTypeDeclaration();
    } else
    {
        throw new IllegalArgumentException((new StringBuilder("Not implemented for node ")).append(member).append(".").toString());
    }
}

public String getIDs(List nodes)
{
    if(nodes == null)
    {
        return "[]";
    }
    StringBuffer sb = new StringBuffer();
    sb.append("[");
    Iterator it = nodes.iterator();
    boolean first = true;
    while(it.hasNext()) 
    {
        if(!first)
        {
            sb.append(", ");
        }
        first = false;
        Object o = it.next();
        if(o instanceof ASTNode)
        {
            sb.append(getID((ASTNode)o));
        } else
        if(o instanceof IBinding)
        {
            sb.append(getID((IBinding)o));
        } else
        {
            throw new IllegalArgumentException("Not all List members are ASTNodes or IBindings");
        }
    }
    sb.append("]");
    return sb.toString();
}

public String getID(IBinding iface)
{
    if(iface == null)
    {
        return "resolve_failed";
    }
    if(localBindings.containsKey(iface))
    {
        return localBindings.get(iface).toString();
    }
    int kind = iface.getKind();
    StringBuffer buff = new StringBuffer();
    buff.append("fqn('");
    switch(kind)
    {
    case 1: // '\001'
        IPackageBinding pb = (IPackageBinding)iface;
        buff.append(normalizeFullQualifiedName(pb.getKey()));
        buff.append("'");
        break;

    case 4: // '\004'
        IMethodBinding mb = (IMethodBinding)iface;
        if(mb.getDeclaringClass().isLocal())
        {
            String resolved = provider.getID();
            localBindings.put(mb, resolved);
            return resolved;
        }
        buff.append(normalizeFullQualifiedName(mb.getDeclaringClass().getTypeDeclaration().getQualifiedName()));
        buff.append("', '");
        if(mb.isConstructor())
        {
            buff.append("<init>");
        } else
        {
            buff.append(mb.getName());
        }
        buff.append("', [");
        boolean first = true;
        ITypeBinding params[] = mb.getParameterTypes();
        for(int i = 0; i < params.length; i++)
        {
            ITypeBinding b = params[i];
            if(!first)
            {
                buff.append(", '");
            } else
            {
                buff.append("'");
            }
            buff.append(b.getQualifiedName());
            first = false;
            buff.append("'");
        }

        buff.append("]");
        break;

    case 2: // '\002'
        ITypeBinding tb = (ITypeBinding)iface;
        buff.append(tb.getErasure().getQualifiedName());
        buff.append("'");
        break;

    case 3: // '\003'
        IVariableBinding vb = (IVariableBinding)iface;
        if(vb.isField())
        {
            if(vb.getDeclaringClass() == null)
            {
                return "'null'";
            }
            if(vb.getDeclaringClass().isLocal())
            {
                String resolved = provider.getID();
                localBindings.put(vb, resolved);
                return resolved;
            }
            buff.append(vb.getDeclaringClass().getQualifiedName());
            buff.append("', '");
            buff.append(vb.getName());
            buff.append("'");
        } else
        {
            Integer theID = (Integer)localBindings.get(vb);
            return theID.toString();
        }
        break;

    default:
        throw new IllegalArgumentException("Unknown kind of Binding");
    }
    buff.append(")");
    return fqnManager.transformFQN(buff.toString());
}

private CompilationUnit getEnclosingCompUnit(ASTNode currentlyProcessedNode)
{
    if(currentlyProcessedNode instanceof CompilationUnit)
    {
        return (CompilationUnit)currentlyProcessedNode;
    } else
    {
        return getEnclosingCompUnit(currentlyProcessedNode.getParent());
    }
}

static String normalizeFullQualifiedName(String key)
{
    if(key.endsWith(";") && key.startsWith("L"))
    {
        key = key.substring(1, key.length() - 1);
    }
    return key.replace('/', '.');
}

public void setEquivalence(ASTNode n1, ASTNode n2)
{
    if(n1 == n2)
    {
        throw new IllegalArgumentException("n1 must be != n2");
    }
    if(n1 == null || n2 == null)
    {
        throw new IllegalArgumentException("No argument may be null");
    }
    if(knownIDs.containsKey(n1) && knownIDs.containsKey(n2))
    {
        throw new IllegalStateException("Both nodes are already mapped");
    }
    if(!knownIDs.containsKey(n1) && !knownIDs.containsKey(n2))
    {
        String id = getID(n1);
        knownIDs.put(n1, id);
        knownIDs.put(n2, id);
    } else
    if(knownIDs.containsKey(n1))
    {
        String id = knownIDs.get(n1).toString();
        knownIDs.put(n2, id);
    } else
    {
        String id = knownIDs.get(n2).toString();
        knownIDs.put(n1, n2);
    }
}

public String getJavaLangObjectID()
{
    return fqnManager.transformFQN("fqn('java.lang.Object')");
}

public String getJavaLangClassID()
{
    return fqnManager.transformFQN("fqn('java.lang.Class')");
}

public String getJavaLangAnnotationAnnotationID()
{
    return fqnManager.transformFQN("fqn('java.lang.annotation.Annotation')");
}

public String getSyntheticConstructorID(ITypeBinding tb)
{
    String key = tb != null ? normalizeFullQualifiedName(tb.getTypeDeclaration().getQualifiedName()) : "java.lang.Object";
    String fqn = (new StringBuilder("fqn('")).append(key).append("', '<init>', [])").toString();
    return fqn;
}

public void setCurrentlyProcessedNode(ASTNode node)
{
    currentlyProcessedNode = node;
}
}
